﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Ewk.Runtime.Serialization.CharacterSeparated
{
    /// <summary>
    /// An <see cref="Attribute"/> that can be used to define a deserialized representation
    /// of a datarecord with fields or values that are separated by a character.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
    public sealed class CharacterSeparatedDatarecordAttribute : DatarecordAttribute
    {
        public CharacterSeparatedDatarecordAttribute(char separationCharacter)
        {
            SeparationCharacter = separationCharacter;
        }

        /// <summary>
        /// Gets of sets a value that is used as character to separate fields in the the string representation of the datarecord.
        /// </summary>
        public char SeparationCharacter { get; private set; }

        protected internal override IEnumerable<DatafieldAttribute> PrepareDatafieldAttributes(IEnumerable<DatafieldAttribute> datafieldAttributes)
        {
            if (datafieldAttributes == null) throw new ArgumentNullException("datafieldAttributes");

            if (datafieldAttributes.Any(attribute => attribute.GetType() != typeof (CharacterSeparatedDatafieldAttribute)))
            {
                var exceptionMessage = string.Format(CultureInfo.CurrentCulture, ExceptionMessages.UnexpectedDatafieldAttribute, typeof(CharacterSeparatedDatafieldAttribute));
                throw new DatafieldTypeException(exceptionMessage);
            }

            var attributeList = datafieldAttributes.Cast<CharacterSeparatedDatafieldAttribute>().ToList();
            attributeList.Sort((a, b) => a.SerialNumber.CompareTo(b.SerialNumber));

            var position = -1;
            foreach (var attribute in attributeList)
            {
                if (attribute.SerialNumber <= position)
                {
                    throw new DatafieldOverlapException(ExceptionMessages.SerialNumberOverlap);
                }

                position = attribute.SerialNumber + attribute.Occurrence - 1;
            }

            return attributeList;
        }

        protected internal override string Serialize<T>(T datarecord, IEnumerable<DatafieldAttribute> datafieldAttributes)
        {
            if (datafieldAttributes == null) throw new ArgumentNullException("datafieldAttributes");

            var resultBuilder = new StringBuilder();

            foreach (var datafieldAttribute in datafieldAttributes)
            {
                var attribute = (CharacterSeparatedDatafieldAttribute)datafieldAttribute;
                if (attribute.SeparationCharacter == default(char))
                {
                    attribute.SeparationCharacter = SeparationCharacter;
                }

                var fieldResult = attribute.Serialize<T>(datarecord);
                resultBuilder.Append(fieldResult);
                resultBuilder.Append(SeparationCharacter);
            }

            var result = resultBuilder.ToString();

            return result.TrimEnd(SeparationCharacter);
        }

        protected internal override T Deserialize<T>(string datarecord, IEnumerable<DatafieldAttribute> datafieldAttributes)
        {
            if (datarecord == null) throw new ArgumentNullException("datarecord");
            if (datafieldAttributes == null) throw new ArgumentNullException("datafieldAttributes");

            var datarecordInstance = (T)typeof(T).Assembly.CreateInstance(typeof(T).FullName);

            var startIndex = 0;
            foreach (var datafieldAttribute in datafieldAttributes)
            {
                var attribute = (CharacterSeparatedDatafieldAttribute)datafieldAttribute;
                if (attribute.SeparationCharacter == default(char))
                {
                    attribute.SeparationCharacter = SeparationCharacter;
                }

                var endIndex = startIndex;
                for (var i = 0; i < attribute.Occurrence && endIndex >= 0; i++)
                {
                    endIndex = datarecord.IndexOf(SeparationCharacter, endIndex) + 1;
                }

                endIndex = endIndex <= 0 ? datarecord.Length : endIndex - 1;

                var field = datarecord.Substring(startIndex, endIndex - startIndex);
                var fieldResult = attribute.Deserialize(field);
                attribute.PropertyInfo.SetValue(datarecordInstance, fieldResult, null);

                startIndex = endIndex + 1;
            }

            return datarecordInstance;
        }
    }
}
